package com.bluefox.modules.security.security;

import com.bluefox.modules.security.config.bean.SecurityProperties;
import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.security.Key;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.stream.Collectors;

/**
 * @CLassName TokenProvider
 * @Description https://jwt.io/introduction/
 * http://www.ibloger.net/article/3075.html
 * jwt的组成
 * 1、Header: 标题包含了令牌的元数据，并且在最小包含签名和/或加密算法的类型
 * 2、Claims: Claims包含您想要签署的任何信息
 * 3、JSON Web Signature (JWS): 在header中指定的使用该算法的数字签名和声明
 * @Author 孙峰
 * @Date 2020/1/1 15:01
 * @Version 1.0
 */
@Slf4j
@Component
public class TokenProvider implements InitializingBean {
    private final SecurityProperties properties;
    private static final String AUTHORITIES_KEY = "auth";
    private Key key;

    public TokenProvider(SecurityProperties properties) {
        this.properties = properties;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        // 本地的密码解码
        byte[] keyBytes = Decoders.BASE64.decode(properties.getBase64Secret());
        // 根据给定的字节数组使用AES加密算法构造一个密钥
        this.key = Keys.hmacShaKeyFor(keyBytes);
    }

    /**
     * 创建token
     *
     * @param authentication
     * @return String
     */
    public String createToken(Authentication authentication) {
        String authorities = authentication.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.joining(","));

        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);

        JwtBuilder builder = Jwts.builder()
                // sub(Subject)：代表这个JWT的主体，即它的所有人，这个是一个json格式的字符串，可以存放什么userId，rouId,userName之类的，作为什么用户的唯一标志。
                .setSubject(authentication.getName())
                // 如果有私有声明，一定要先设置这个自己创建的私有的声明，这个是给builder的claim赋值，一旦写在标准的声明赋值之后，就是覆盖了那些标准的声明的
                //签署的信息 用户的权限
                .claim(AUTHORITIES_KEY, authorities)
                // 指定签名的时候使用的签名算法，也就是header那部分，jjwt已经将这部分内容封装好了。
                .signWith(SignatureAlgorithm.HS512, key)
                //// iat: jwt的签发时间
                .setIssuedAt(now);
        // 设置过期时间
        if (properties.getTokenValidityInSeconds() >= 0) {
            long expMillis = nowMillis + properties.getTokenValidityInSeconds();
            Date exp = new Date(expMillis);
            builder.setExpiration(exp);
        }
        return builder.compact();
    }

    /**
     * 根据token获取认证信息
     *
     * @param token
     * @return Authentication
     */
    Authentication getAuthentication(String token) {
        Claims claims = Jwts.parser()
                .setSigningKey(key)
                .parseClaimsJws(token)
                .getBody();
        Collection<? extends GrantedAuthority> authorities =
                Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(","))
                        .map(SimpleGrantedAuthority::new)
                        .collect(Collectors.toList());
        //User是UserDetails的实现
        User principal = new User(claims.getSubject(), "", authorities);

        return new UsernamePasswordAuthenticationToken(principal, token, authorities);
    }


    boolean validateToken(String authToken) {
        try {
            Jwts.parser().setSigningKey(key) // 签名算法以及密匙
                    .parseClaimsJws(authToken);
            return true;
        } catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) {
            log.info("Invalid JWT signature.");
            e.printStackTrace();
        } catch (ExpiredJwtException e) {
            log.info("Expired JWT token.");
            e.printStackTrace();
        } catch (UnsupportedJwtException e) {
            log.info("Unsupported JWT token.");
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            log.info("JWT token compact of handler are invalid.");
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 从请求头里面获取token值
     *
     * @param request
     * @return
     */
    public String getToken(HttpServletRequest request) {
        final String requestHeader = request.getHeader(properties.getHeader());
        if (requestHeader != null && requestHeader.startsWith(properties.getTokenStartWith())) {
            return requestHeader.substring(7);
        }
        return null;
    }
}
